package io.github.tuuzed.hydrogen;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import io.github.tuuzed.hydrogen.annotation.Column;
import io.github.tuuzed.hydrogen.annotation.Table;
import io.github.tuuzed.hydrogen.util.Logger;

public class Hydrogen {
    private static final String TAG = "Hydrogen";
    private Map<Class<? extends Model>, List<ColumnInfo>> mColumns;
    private Map<Class<? extends Model>, String> mTableNames;
    private SQLiteOpenHelper mDatabaseHelper;
    private SQLiteDatabase mWritableDatabase;
    private SQLiteDatabase mReadableDatabase;
    private HashMap<Class<?>, SQLiteType> mType;

    private String mDbName;
    private int mVersion = 1;
    private UpgradeCallback mCallback = null;
    private SQLiteDatabase.CursorFactory mFactory = null;
    private List<Class<? extends Model>> mModelList = new ArrayList<>();

    private final static Hydrogen singleton = new Hydrogen();

    private Hydrogen() {
        mColumns = new HashMap<>();
        mTableNames = new HashMap<>();
        mType = new HashMap<Class<?>, SQLiteType>() {
            {
                put(byte.class, SQLiteType.INTEGER);
                put(short.class, SQLiteType.INTEGER);
                put(int.class, SQLiteType.INTEGER);
                put(long.class, SQLiteType.INTEGER);
                put(float.class, SQLiteType.REAL);
                put(double.class, SQLiteType.REAL);
                put(boolean.class, SQLiteType.INTEGER);
                put(char.class, SQLiteType.TEXT);
                put(byte[].class, SQLiteType.BLOB);
                put(Byte.class, SQLiteType.INTEGER);
                put(Short.class, SQLiteType.INTEGER);
                put(Integer.class, SQLiteType.INTEGER);
                put(Long.class, SQLiteType.INTEGER);
                put(Float.class, SQLiteType.REAL);
                put(Double.class, SQLiteType.REAL);
                put(Boolean.class, SQLiteType.INTEGER);
                put(Character.class, SQLiteType.TEXT);
                put(String.class, SQLiteType.TEXT);
                put(Byte[].class, SQLiteType.BLOB);
            }
        };
    }

    public static Hydrogen getInstance() {
        return singleton;
    }

    public Hydrogen setDebug(boolean debug) {
        Logger.setDebug(debug);
        return this;
    }

    public Hydrogen setmDbName(String dbName) {
        this.mDbName = dbName;
        return this;
    }

    public Hydrogen setVersion(int version, UpgradeCallback callback) {
        this.mVersion = version;
        this.mCallback = callback;
        return this;
    }

    public Hydrogen setCursorFactory(SQLiteDatabase.CursorFactory factory) {
        this.mFactory = factory;
        return this;
    }

    public Hydrogen addModel(Class<? extends Model> clazz) {
        this.mModelList.add(clazz);
        return this;
    }

    public void create(Context context) {
        mDatabaseHelper = new SQLiteOpenHelper(context.getApplicationContext(), mDbName, mFactory, mVersion) {
            @Override
            public void onCreate(SQLiteDatabase db) {
                for (Class<? extends Model> clazz : mModelList) {
                    db.execSQL(buildCreateTableSql(clazz));
                }
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
                if (newVersion > oldVersion) {
                    if (mCallback != null) {
                        db.execSQL(mCallback.getUpgradeSQL());
                    }
                }
            }
        };
    }

    public synchronized void terminate() {
        if (mWritableDatabase != null) {
            if (mWritableDatabase.isOpen()) {
                mWritableDatabase.close();
            }
            mWritableDatabase = null;
        }
        if (mReadableDatabase != null) {
            if (mReadableDatabase.isOpen()) {
                mReadableDatabase.close();
            }
            mReadableDatabase = null;
        }
        if (mDatabaseHelper != null) {
            mDatabaseHelper.close();
            mDatabaseHelper = null;
        }
    }

    synchronized SQLiteDatabase getWritableDatabase() {
        // 如果 SQLiteDatabase对象为Null 或者关闭则重新打开数据库
        if (mWritableDatabase == null || !mWritableDatabase.isOpen()) {
            mWritableDatabase = mDatabaseHelper.getWritableDatabase();
        }
        return mWritableDatabase;
    }

    synchronized SQLiteDatabase getReadableDatabase() {
        // 如果 SQLiteDatabase对象为Null 或者关闭则重新打开数据库
        if (mReadableDatabase == null || !mReadableDatabase.isOpen()) {
            mReadableDatabase = mDatabaseHelper.getReadableDatabase();
        }
        return mReadableDatabase;
    }

    List<ColumnInfo> getColumnInfoList(Class<? extends Model> clazz) {
        List<ColumnInfo> columnInfoList = mColumns.get(clazz);
        if (columnInfoList == null) {
            columnInfoList = new ArrayList<>();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Column column = field.getAnnotation(Column.class);
                if (column == null) continue;
                String columnName = column.value();
                boolean isNullable = column.nullable();
                columnInfoList.add(new ColumnInfo(columnName, field, isNullable));
            }
            mColumns.put(clazz, columnInfoList);
        }
        return columnInfoList;
    }

    String getTableName(Class<? extends Model> clazz) {
        String tableName = mTableNames.get(clazz);
        if (tableName == null) {
            tableName = clazz.getAnnotation(Table.class).value().toUpperCase();
            mTableNames.put(clazz, tableName);
        }
        return tableName;
    }

    // 构建建表语句
    private String buildCreateTableSql(Class<? extends Model> clazz) {
        StringBuilder sb = new StringBuilder();
        for (ColumnInfo columnInfo : getColumnInfoList(clazz)) {
            String columnName = columnInfo.getColumnName();
            boolean isNullable = columnInfo.isNullable();
            Field field = columnInfo.getField();
            sb.append(toSql(columnName, isNullable, field));
        }
        return String.format(
                "CREATE TABLE IF NOT EXISTS %s(_id INTEGER PRIMARY KEY AUTOINCREMENT%s)",
                getTableName(clazz), sb.toString());
    }

    private String toSql(String columnName, boolean isNullable, Field field) {
        String sql = String.format(" ,%s %s", columnName, mType.get(field.getType()));
        if (isNullable) {
            sql += " NULL";
        } else {
            sql += " NOT NULL";
        }
        return sql;
    }


    private enum SQLiteType {
        INTEGER, REAL, TEXT, BLOB
    }

    static class ColumnInfo {
        private String columnName;
        private Field field;
        private boolean nullable;

        ColumnInfo(String columnName, Field field, boolean nullable) {
            this.columnName = columnName;
            this.field = field;
            this.nullable = nullable;
        }

        String getColumnName() {
            return columnName;
        }

        Field getField() {
            return field;
        }

        boolean isNullable() {
            return nullable;
        }
    }

    public interface UpgradeCallback {
        String getUpgradeSQL();
    }
}
